/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2003-2018 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ivl_alloc.h"
#include "sys_priv.h"

#define IS_MCD(mcd) !((mcd) >> 31 & 1)

/*
 * Implement the $fopen system function.
 */
static PLI_INT32 sys_fopen_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv;
  vpiHandle arg;
  assert(callh != 0);
  argv = vpi_iterate(vpiArgument, callh);

  /* Check that there is a file name argument and that it is a string. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a string file name argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  if (!is_string_obj(vpi_scan(argv))) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's file name argument must be a string.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* The type argument is optional. */
  arg = vpi_scan(argv);
  if (arg == 0) return 0;

  /* When provided, the type argument must be a string. */
  if (!is_string_obj(arg)) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's type argument must be a string.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "two string arguments", 1);

  return 0;
}

static PLI_INT32 sys_fopen_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  s_vpi_value val;
  int fail = 0;
  char *mode_string = 0;
  vpiHandle fileh = vpi_scan(argv);
  char *fname;
  vpiHandle mode = vpi_scan(argv);
  errno = 0;

  /* Get the mode handle if it exists. */
  if (mode) {
    char *esc_md;
    val.format = vpiStringVal;
    vpi_get_value(mode, &val);
    /* Verify that we have a string and that it is not NULL. */
    if (val.format != vpiStringVal || !*(val.value.str)) {
      vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's mode argument is not a valid string.\n", name);
      fail = 1;
    }

    /* Make sure the mode string is correct. */
    if (strlen(val.value.str) > 3) {
      vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      esc_md = as_escaped(val.value.str);
      vpi_printf("%s's mode argument (%s) is too long.\n", name, esc_md);
      free(esc_md);
      fail = 1;
    } else {
      unsigned bin = 0, plus = 0, idx;
      switch (val.value.str[0]) {
        case 'r':
        case 'w':
        case 'a':
          for (idx = 1; idx < 3; idx++) {
            if (val.value.str[idx] == '\0') break;
            switch (val.value.str[idx]) {
              case 'b':
                if (bin) fail = 1;
                bin = 1;
                break;
              case '+':
                if (plus) fail = 1;
                plus = 1;
                break;
              default:
                fail = 1;
                break;
            }
          }
          if (!fail) break;
          // fallthrough
        default:
          vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                     (int)vpi_get(vpiLineNo, callh));
          esc_md = as_escaped(val.value.str);
          vpi_printf("%s's mode argument (%s) is invalid.\n", name, esc_md);
          free(esc_md);
          fail = 1;
          break;
      }
    }

    mode_string = strdup(val.value.str);

    vpi_free_object(argv);
  }

  fname = get_filename(callh, name, fileh);

  /* If either the mode or file name are not valid just return. */
  if (fail || fname == 0) {
    free(fname);
    if (mode) free(mode_string);
    return 0;
  }

  val.format = vpiIntVal;
  if (mode) {
    val.value.integer = vpi_fopen(fname, mode_string);
    free(mode_string);
  } else
    val.value.integer = vpi_mcd_open(fname);

  vpi_put_value(callh, &val, 0, vpiNoDelay);
  free(fname);

  return 0;
}

/*
 * Implement the $fopenr(), $fopenw() and $fopena() system functions
 * from Chris Spear's File I/O for Verilog.
 */

static PLI_INT32 sys_fopenrwa_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  s_vpi_value val;
  char *fname;
  const char *mode;
  errno = 0;

  /* Get the mode. */
  mode = name + strlen(name) - 1;

  /* Get the file name. */
  fname = get_filename(callh, name, vpi_scan(argv));
  vpi_free_object(argv);
  if (fname == 0) return 0;

  /* Open the file and return the result. */
  val.format = vpiIntVal;
  val.value.integer = vpi_fopen(fname, mode);
  vpi_put_value(callh, &val, 0, vpiNoDelay);
  free(fname);

  return 0;
}

/*
 * Implement $fclose system function
 */
static PLI_INT32 sys_fclose_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle fd = vpi_scan(argv);
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  errno = 0;

  vpi_free_object(argv);

  /* Get the file/MC descriptor and verify that it is valid. */
  val.format = vpiIntVal;
  vpi_get_value(fd, &val);
  fd_mcd = val.value.integer;

  if ((!IS_MCD(fd_mcd) && vpi_get_file(fd_mcd) == NULL) ||
      (IS_MCD(fd_mcd) && vpi_mcd_printf(fd_mcd, "%s", "") == EOF) ||
      (!fd_mcd)) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor/MCD (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    return 0;
  }

  /* We need to cancel any active $fstrobe()'s for this FD/MCD.
   * For now we check in the strobe callback and skip the output
   * generation when needed. */
  vpi_mcd_close(fd_mcd);

  return 0;
}

/*
 * Implement $fflush system function
 */
static PLI_INT32 sys_fflush_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  errno = 0;

  /* If we have no argument then flush all the streams. */
  if (argv == 0) {
    fflush(NULL);
    return 0;
  }

  /* Get the file/MC descriptor and verify that it is valid. */
  arg = vpi_scan(argv);
  vpi_free_object(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* If the MCD is zero we have nothing to do so just return. */
  if (fd_mcd == 0) return 0;

  if ((!IS_MCD(fd_mcd) && vpi_get_file(fd_mcd) == NULL) ||
      (IS_MCD(fd_mcd) && vpi_mcd_printf(fd_mcd, "%s", "") == EOF) ||
      (!fd_mcd)) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor/MCD (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    return 0;
  }

  if (IS_MCD(fd_mcd)) {
    vpi_mcd_flush(fd_mcd);
  } else {
    /* If we have a valid file descriptor flush the file. */
    FILE *fp = vpi_get_file(fd_mcd);
    if (fp) fflush(fp);
  }

  return 0;
}

static PLI_INT32 sys_fputc_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  FILE *fp;
  unsigned char chr;
  errno = 0;

  /* Get the character. */
  arg = vpi_scan(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  chr = val.value.integer;

  /* Get the file/MC descriptor. */
  arg = vpi_scan(argv);
  vpi_free_object(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* Put the character and return the result. */
  fp = vpi_get_file(fd_mcd);
  val.format = vpiIntVal;
  if (!fp) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    val.value.integer = EOF;
  } else {
    val.value.integer = fputc(chr, fp);
    if (val.value.integer != EOF) val.value.integer = 0;
  }
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  return 0;
}

static PLI_INT32 sys_fgets_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /*
   * Check that there are two arguments and that the first is a
   * register and that the second is numeric.
   */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires two arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (vpi_get(vpiType, vpi_scan(argv)) != vpiReg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be a reg.\n", name);
    vpi_control(vpiFinish, 1);
  }

  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a second (numeric) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (!is_numeric_obj(arg)) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's second argument must be numeric.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "two arguments", 0);

  return 0;
}

static PLI_INT32 sys_fgets_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle regh;
  vpiHandle arg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  FILE *fp;
  PLI_INT32 reg_size;
  char *text;
  errno = 0;

  /* Get the register handle. */
  regh = vpi_scan(argv);

  /* Get the file/MCD descriptor. */
  arg = vpi_scan(argv);
  vpi_free_object(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* Return zero if this is not a valid fd. */
  fp = vpi_get_file(fd_mcd);
  if (!fp) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    val.format = vpiIntVal;
    val.value.integer = 0;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Get the register size in bytes and allocate the buffer. */
  reg_size = vpi_get(vpiSize, regh) / 8;
  text = malloc(reg_size + 1);

  /* Read in the bytes. Return 0 if there was an error. */
  if (fgets(text, reg_size + 1, fp) == 0) {
    val.format = vpiIntVal;
    val.value.integer = 0;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
    free(text);
    return 0;
  }

  /* Return the number of character read. */
  val.format = vpiIntVal;
  val.value.integer = strlen(text);
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  /* Return the characters to the register. */
  val.format = vpiStringVal;
  val.value.str = text;
  vpi_put_value(regh, &val, 0, vpiNoDelay);
  free(text);

  return 0;
}

static PLI_INT32 sys_fread_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  PLI_INT32 type;

  /* We must have at least two arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires two arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* Check that the first required argument is a register or memory. */
  type = vpi_get(vpiType, vpi_scan(argv));
  if (type != vpiReg && type != vpiMemory) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be a reg or memory.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Check that the second required argument is numeric (a fd). */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a second (file descriptor) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (!is_numeric_obj(arg)) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's second argument must be numeric.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /*
   * If given check that the third argument is numeric (start).
   *
   * Technically you can give the fourth argument (count) with
   * out a third argument (start), but Icarus does not currently
   * support missing function arguments!
   */
  arg = vpi_scan(argv);
  if (arg) {
    if (!is_numeric_obj(arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's third argument must be numeric.\n", name);
      vpi_control(vpiFinish, 1);
    }

    /* If given check that the fourth argument is numeric (count). */
    arg = vpi_scan(argv);
    if (arg) {
      if (!is_numeric_obj(arg)) {
        vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                   (int)vpi_get(vpiLineNo, callh));
        vpi_printf("%s's fourth argument must be numeric.\n", name);
        vpi_control(vpiFinish, 1);
      }

      /* Make sure there are no extra arguments. */
      check_for_extra_args(argv, callh, name, "four arguments", 1);
    }
  }

  return 0;
}

/*
 * The pattern here is get the current vector, load the new bits on
 * top of the old ones and then put the modified vector. We need the
 * "get" first so that if we run out of bits in the file we keep the
 * original ones.
 */
static unsigned fread_word(FILE *fp, vpiHandle word, unsigned words,
                           unsigned bpe, s_vpi_vecval *vector) {
  int bidx;
  s_vpi_value val;
  struct t_vpi_vecval *cur = &vector[words - 1];
  unsigned rtn = 0;

  /* Get the current bits from the register and copy them to
   * my local vector. */
  val.format = vpiVectorVal;
  vpi_get_value(word, &val);
  for (bidx = 0; (unsigned)bidx < words; bidx += 1) {
    vector[bidx].aval = val.value.vector[bidx].aval;
    vector[bidx].bval = val.value.vector[bidx].bval;
  }

  /* Copy the bytes to the local vector MSByte first. */
  for (bidx = bpe - 1; bidx >= 0; bidx -= 1) {
    unsigned clr_mask, bnum;
    int byte = fgetc(fp);
    if (byte == EOF) break;
    /* Clear the current byte and load the new value. */
    bnum = bidx % 4;
    clr_mask = ~(0xff << bnum * 8);
    cur->aval &= clr_mask;
    cur->bval &= clr_mask;
    cur->aval |= byte << bnum * 8;
    rtn += 1;
    if (bnum == 0) cur -= 1;
  }

  /* Put the updated bits into the register. */
  val.value.vector = vector;
  vpi_put_value(word, &val, 0, vpiNoDelay);

  return rtn;
}

static PLI_INT32 sys_fread_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg, mem_reg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  PLI_INT32 start, count, width, rtn;
  unsigned is_mem, bpe, words;
  FILE *fp;
  s_vpi_vecval *vector;
  errno = 0;

  /* Get the register/memory. */
  mem_reg = vpi_scan(argv);

  /* Get the file descriptor. */
  arg = vpi_scan(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* Return 0 if this is not a valid fd. */
  fp = vpi_get_file(fd_mcd);
  if (!fp) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    val.format = vpiIntVal;
    val.value.integer = 0;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
    vpi_free_object(argv);
    return 0;
  }

  /* Are we reading into a memory? */
  if (vpi_get(vpiType, mem_reg) == vpiReg)
    is_mem = 0;
  else
    is_mem = 1;

  /* We only need to get these for memories. */
  if (is_mem) {
    PLI_INT32 left, right, max, min;

    /* Get the left and right memory address. */
    val.format = vpiIntVal;
    vpi_get_value(vpi_handle(vpiLeftRange, mem_reg), &val);
    left = val.value.integer;
    val.format = vpiIntVal;
    vpi_get_value(vpi_handle(vpiRightRange, mem_reg), &val);
    right = val.value.integer;
    max = (left > right) ? left : right;
    min = (left < right) ? left : right;

    /* Get the starting address (optional). */
    arg = vpi_scan(argv);
    if (arg) {
      val.format = vpiIntVal;
      vpi_get_value(arg, &val);
      start = val.value.integer;
      if (start < min || start > max) {
        vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                   (int)vpi_get(vpiLineNo, callh));
        vpi_printf(
            "%s's start argument (%d) is outside "
            "memory range [%d:%d].\n",
            name, (int)start, (int)left, (int)right);
        val.format = vpiIntVal;
        val.value.integer = 0;
        vpi_put_value(callh, &val, 0, vpiNoDelay);
        vpi_free_object(argv);
        return 0;
      }

      /* Get the count (optional). */
      arg = vpi_scan(argv);
      if (arg) {
        val.format = vpiIntVal;
        vpi_get_value(arg, &val);
        count = val.value.integer;
        if (count > max - start) {
          vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                     (int)vpi_get(vpiLineNo, callh));
          vpi_printf(
              "%s's count argument (%d) is too "
              "large for start (%d) and memory "
              "range [%d:%d].\n",
              name, (int)count, (int)start, (int)left, (int)right);
          count = max - start + 1;
        }
        vpi_free_object(argv);
      } else {
        count = max - start + 1;
      }
    } else {
      start = min;
      count = max - min + 1;
    }
    width = vpi_get(vpiSize, vpi_handle_by_index(mem_reg, start));
  } else {
    start = 0;
    count = 1;
    width = vpi_get(vpiSize, mem_reg);
    vpi_free_object(argv);
  }

  assert(width > 0);
  words = (width - 1) / 32 + 1;
  vector = calloc(words, sizeof(s_vpi_vecval));
  bpe = (width + 7) / 8;

  assert(count >= 0);
  if (is_mem) {
    unsigned idx;
    rtn = 0;
    for (idx = 0; idx < (unsigned)count; idx += 1) {
      vpiHandle word;
      word = vpi_handle_by_index(mem_reg, start + (signed)idx);
      rtn += fread_word(fp, word, words, bpe, vector);
      if (feof(fp)) break;
    }
  } else {
    rtn = fread_word(fp, mem_reg, words, bpe, vector);
  }
  free(vector);

  /* Return the number of bytes read. */
  val.format = vpiIntVal;
  val.value.integer = rtn;
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  return 0;
}

static PLI_INT32 sys_ungetc_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  FILE *fp;
  int chr;
  errno = 0;

  /* Get the character. */
  arg = vpi_scan(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  chr = val.value.integer;

  /* Get the file/MC descriptor. */
  arg = vpi_scan(argv);
  vpi_free_object(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* Return EOF if this is not a valid fd. */
  fp = vpi_get_file(fd_mcd);
  if (!fp) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    val.format = vpiIntVal;
    val.value.integer = EOF;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
    return 0;
  }

  /* ungetc the character and return the result. */
  val.format = vpiIntVal;
  val.value.integer = ungetc(chr, fp);
  if (val.value.integer != EOF) val.value.integer = 0;
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  return 0;
}

static PLI_INT32 sys_fseek_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /* Check that there are three numeric arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires three arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* Check that the first argument is numeric. */
  if (!is_numeric_obj(vpi_scan(argv))) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be numeric.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Check that the second argument exists and is numeric. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a second (numeric) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (!is_numeric_obj(arg)) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's second argument must be numeric.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Check that the third argument exists and is numeric. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a third (numeric) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (!is_numeric_obj(arg)) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's third argument must be numeric.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "three arguments", 0);

  return 0;
}

static PLI_INT32 sys_fseek_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  PLI_INT32 offset, oper;
  FILE *fp;
  errno = 0;

  /* Get the file pointer. */
  arg = vpi_scan(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* Get the offset. */
  arg = vpi_scan(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  offset = val.value.integer;

  /* Get the operation. */
  arg = vpi_scan(argv);
  vpi_free_object(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  oper = val.value.integer;

  /* Check that the operation is in the valid range. */
  switch (oper) {
    case 0:
      oper = SEEK_SET;
      break;
    case 1:
      oper = SEEK_CUR;
      break;
    case 2:
      oper = SEEK_END;
      break;
    default:
      vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's operation must be 0, 1 or 2 given %d.\n", name,
                 (int)oper);
      oper = -1; /* An invalid argument value. */
  }

  /* Return EOF if this is not a valid fd. */
  fp = vpi_get_file(fd_mcd);
  if (!fp) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    val.format = vpiIntVal;
    val.value.integer = EOF;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
    return 0;
  }

  val.format = vpiIntVal;
#if defined(__GNUC__)
  val.value.integer = fseek(fp, offset, oper);
#else
  if (oper < 0) {
    val.value.integer = EOF;
    errno = EINVAL;
  } else
    val.value.integer = fseek(fp, offset, oper);
#endif
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  return 0;
}

static PLI_INT32 sys_common_fd_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  s_vpi_value val;
  PLI_UINT32 fd_mcd;
  FILE *fp;
  errno = 0;

  /* Get the file pointer. */
  arg = vpi_scan(argv);
  vpi_free_object(argv);
  val.format = vpiIntVal;
  vpi_get_value(arg, &val);
  fd_mcd = val.value.integer;

  /* Return EOF if this is not a valid fd. */
  fp = vpi_get_file(fd_mcd);
  if (!fp) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
    val.format = vpiIntVal;
    val.value.integer = EOF;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
    return 0;
  }

  val.format = vpiIntVal;
  switch (name[4]) {
    case 'l': /* $ftell() */
      val.value.integer = ftell(fp);
      break;
    case 'f': /* $feof() is from 1264-2005*/
      val.value.integer = feof(fp);
      break;
    case 'i': /* $rewind() */
      val.value.integer = fseek(fp, 0L, SEEK_SET);
      break;
    case 't': /* $fgetc() */
      val.value.integer = fgetc(fp);
      break;
    default:
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s cannot be processed with this routine.\n", name);
      assert(0);
      break;
  }
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  return 0;
}

/*
 * Implement the $ferror system function.
 */
static PLI_INT32 sys_ferror_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv;
  vpiHandle arg;

  argv = vpi_iterate(vpiArgument, callh);

  /*
   * Check that there are two arguments and that the first is
   * numeric and that the second is a 640 bit or larger register.
   *
   * The parser requires that a function have at least one argument,
   * so argv should always be defined with one argument.
   */
  assert(argv);

  if (!is_numeric_obj(vpi_scan(argv))) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's fd (first) argument must be numeric.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Check that the second argument is given and that it is a 640 bit
   * or larger register. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a second (register) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (vpi_get(vpiType, arg) != vpiReg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's second argument must be a reg (>=640 bits).\n", name);
    vpi_control(vpiFinish, 1);
  } else if (vpi_get(vpiSize, arg) < 640) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's second argument must have 640 bit or more.\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "two arguments", 0);

  return 0;
}

static PLI_INT32 sys_ferror_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle reg;
  s_vpi_value val;
  char *msg;
  PLI_INT32 size;
  unsigned chars;
  PLI_UINT32 fd_mcd;

  /* Get the file pointer. */
  val.format = vpiIntVal;
  vpi_get_value(vpi_scan(argv), &val);
  fd_mcd = val.value.integer;

  /* Get the register to put the string result and figure out how many
   * characters it will hold. */
  reg = vpi_scan(argv);
  size = vpi_get(vpiSize, reg);
  chars = size / 8;
  vpi_free_object(argv);

  /* If we do not already have an error check that the fd is valid.
   * The assumption is that the other routines have set errno to
   * EBADF when they encounter a bad file descriptor, so we do not
   * need to check here. We also need to special case this since
   * $fopen() will return 0 (a bad file descriptor) when it has a
   * problem (sets errno). */
  if (!errno && !vpi_get_file(fd_mcd)) {
    vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("invalid file descriptor (0x%x) given to %s.\n",
               (unsigned int)fd_mcd, name);
    errno = EBADF;
  }

  /* Return the error code. */
  val.format = vpiIntVal;
  val.value.integer = errno;
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  /* Only return the number of characters that will fit in the reg. */
  msg = (char *)malloc(chars);
  if (errno != 0)
    strncpy(msg, strerror(errno), chars - 1);
  else
    strncpy(msg, "", chars - 1);
  msg[chars - 1] = '\0';
  val.format = vpiStringVal;
  val.value.str = msg;
  vpi_put_value(reg, &val, 0, vpiNoDelay);
  free(msg);

  return 0;
}

void sys_fileio_register(void) {
  s_vpi_systf_data tf_data;
  vpiHandle res;

  /*============================== fopen */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fopen";
  tf_data.calltf = sys_fopen_calltf;
  tf_data.compiletf = sys_fopen_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fopen";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fopenr */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fopenr";
  tf_data.calltf = sys_fopenrwa_calltf;
  tf_data.compiletf = sys_one_string_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fopenr";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fopenw */
  tf_data.tfname = "$fopenw";
  tf_data.user_data = "$fopenw";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fopena */
  tf_data.tfname = "$fopena";
  tf_data.user_data = "$fopena";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fclose */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fclose";
  tf_data.calltf = sys_fclose_calltf;
  tf_data.compiletf = sys_one_numeric_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fclose";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fflush */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fflush";
  tf_data.calltf = sys_fflush_calltf;
  tf_data.compiletf = sys_one_opt_numeric_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fflush";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fgetc */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fgetc";
  tf_data.calltf = sys_common_fd_calltf;
  tf_data.compiletf = sys_one_numeric_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fgetc";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fgets */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fgets";
  tf_data.calltf = sys_fgets_calltf;
  tf_data.compiletf = sys_fgets_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fgets";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fread */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fread";
  tf_data.calltf = sys_fread_calltf;
  tf_data.compiletf = sys_fread_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fread";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== ungetc */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$ungetc";
  tf_data.calltf = sys_ungetc_calltf;
  tf_data.compiletf = sys_two_numeric_args_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$ungetc";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== ftell */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$ftell";
  tf_data.calltf = sys_common_fd_calltf;
  tf_data.compiletf = sys_one_numeric_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$ftell";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fseek */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fseek";
  tf_data.calltf = sys_fseek_calltf;
  tf_data.compiletf = sys_fseek_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fseek";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);
  vpip_make_systf_system_defined(res);

  /*============================== rewind */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$rewind";
  tf_data.calltf = sys_common_fd_calltf;
  tf_data.compiletf = sys_one_numeric_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$rewind";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== ferror */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$ferror";
  tf_data.calltf = sys_ferror_calltf;
  tf_data.compiletf = sys_ferror_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$ferror";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /* $feof() is from 1364-2005. */
  /*============================== feof */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$feof";
  tf_data.calltf = sys_common_fd_calltf;
  tf_data.compiletf = sys_one_numeric_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$feof";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /* Icarus specific. */
  /*============================== fputc */
  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiIntFunc;
  tf_data.tfname = "$fputc";
  tf_data.calltf = sys_fputc_calltf;
  tf_data.compiletf = sys_two_numeric_args_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fputc";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);
}
